use crate::db::{dao, po};
use crate::dto;
use crate::service::error::Error;
use anyhow::Context;
use std::sync::Arc;
use wanmesh_common::error::ResponseError;

#[async_trait::async_trait]
pub trait BaseEndpointService: Send + Sync {
    async fn list(
        &self,
        filter: &dto::core::endpoint::Filter,
        offset: u64,
        limit: u64,
    ) -> Result<Vec<dto::core::endpoint::EndpointItem>, Error>;

    async fn get(&self, endpoint_id: &str) -> Result<dto::core::endpoint::EndpointItem, Error>;

    async fn try_get(
        &self,
        endpoint_id: &str,
    ) -> Result<Option<dto::core::endpoint::EndpointItem>, Error>;

    async fn update(
        &self,
        endpoint_id: &str,
        params: &dto::core::endpoint::UpdateParams,
    ) -> Result<(), Error>;

    async fn delete(&self, endpoint_id: &str) -> Result<(), Error>;

    async fn count(&self, filter: &dto::core::endpoint::Filter) -> Result<u64, Error>;
    async fn exists(&self, filter: &dto::core::endpoint::Filter) -> Result<bool, Error>;
}

#[async_trait::async_trait]
pub trait EndpointServiceAdd: Send + Sync {
    async fn add(
        &self,
        params: &dto::core::endpoint::AddParams,
    ) -> Result<dto::core::endpoint::AddResponseData, Error>;
}

#[async_trait::async_trait]
pub trait EndpointService: EndpointServiceAdd + BaseEndpointService {}

pub struct DefaultEndpointService {
    endpoint_dao: Arc<dyn dao::endpoint::EndpointDao>,
}

impl DefaultEndpointService {
    pub fn new(endpoint_dao: Arc<dyn dao::endpoint::EndpointDao>) -> Self {
        Self { endpoint_dao }
    }
}

#[async_trait::async_trait]
impl EndpointServiceAdd for DefaultEndpointService {
    async fn add(
        &self,
        params: &dto::core::endpoint::AddParams,
    ) -> Result<dto::core::endpoint::AddResponseData, Error> {
        let endpoint_id = wanmesh_common::util::new_id()?;

        let (ipv4_address, ipv4_prefix_length) = match &params.ipv4 {
            Some(ipv4) => (
                po::ActiveValue::Set(Some(ipv4.address.address().into())),
                po::ActiveValue::Set(Some(ipv4.address.network_length())),
            ),
            None => (po::ActiveValue::NotSet, po::ActiveValue::NotSet),
        };

        let (ipv6_address, ipv6_prefix_length) = match &params.ipv6 {
            Some(ipv6) => (
                po::ActiveValue::Set(Some(ipv6.address.address().into())),
                po::ActiveValue::Set(Some(ipv6.address.network_length())),
            ),
            None => (po::ActiveValue::NotSet, po::ActiveValue::NotSet),
        };

        self.endpoint_dao
            .insert(po::endpoint::ActiveModel {
                id: Default::default(),
                node_id: po::ActiveValue::Set(params.node_id.clone()),
                endpoint_id: po::ActiveValue::Set(endpoint_id.clone()),
                endpoint_type: po::ActiveValue::Set(params.endpoint_type.as_u32()),
                name: po::ActiveValue::Set(params.name.clone()),
                description: po::ActiveValue::Set(params.description.clone()),
                ipv4_address,
                ipv4_prefix_length,
                ipv6_address,
                ipv6_prefix_length,
                create_timestamp_millis: Default::default(),
                update_timestamp_millis: Default::default(),
            })
            .await?;

        Ok(dto::core::endpoint::AddResponseData { endpoint_id })
    }
}

#[async_trait::async_trait]
impl BaseEndpointService for DefaultEndpointService {
    async fn list(
        &self,
        filter: &dto::core::endpoint::Filter,
        offset: u64,
        limit: u64,
    ) -> Result<Vec<dto::core::endpoint::EndpointItem>, Error> {
        let filter = dao::endpoint::Filter::from(filter);

        let po_list = self
            .endpoint_dao
            .find(dao::FindParams {
                filter: filter.into(),
                offset: Some(offset),
                limit: Some(limit),
                ..Default::default()
            })
            .await?;

        let list = wanmesh_common::util::slice_try_to(po_list.as_slice())?;

        Ok(list)
    }

    async fn get(&self, endpoint_id: &str) -> Result<dto::core::endpoint::EndpointItem, Error> {
        let endpoint = self
            .try_get(endpoint_id)
            .await?
            .ok_or(ResponseError::endpoint_not_found(endpoint_id))?;
        Ok(endpoint)
    }

    async fn try_get(
        &self,
        endpoint_id: &str,
    ) -> Result<Option<dto::core::endpoint::EndpointItem>, Error> {
        let filter = dao::endpoint::Filter {
            endpoint_id: Some(endpoint_id.to_string()),
            ..Default::default()
        };

        if let Some(m) = self.endpoint_dao.try_get(filter).await? {
            return Ok(Some(m.try_into()?));
        }

        Ok(None)
    }

    async fn update(
        &self,
        endpoint_id: &str,
        params: &dto::core::endpoint::UpdateParams,
    ) -> Result<(), Error> {
        if params == &Default::default() {
            return Ok(());
        }

        let filter = dao::endpoint::Filter {
            endpoint_id: Some(endpoint_id.to_string()),
            ..Default::default()
        };

        self.endpoint_dao.update(filter, params.into()).await?;

        Ok(())
    }

    async fn delete(&self, endpoint_id: &str) -> Result<(), Error> {
        let filter = dao::endpoint::Filter {
            endpoint_id: Some(endpoint_id.to_string()),
            ..Default::default()
        };
        self.endpoint_dao.delete(filter).await?;
        Ok(())
    }

    async fn count(&self, filter: &dto::core::endpoint::Filter) -> Result<u64, Error> {
        let count = self.endpoint_dao.count(filter.into()).await?;
        Ok(count)
    }

    async fn exists(&self, filter: &dto::core::endpoint::Filter) -> Result<bool, Error> {
        let exists = self.endpoint_dao.exists(filter.into()).await?;

        Ok(exists)
    }
}

#[async_trait::async_trait]
impl EndpointService for DefaultEndpointService {}

impl TryFrom<&po::endpoint::Model> for dto::core::endpoint::EndpointItem {
    type Error = anyhow::Error;
    fn try_from(value: &po::endpoint::Model) -> Result<Self, Self::Error> {
        let mut ipv4: Option<dto::ipv4::Ipv4> = None;
        let mut ipv6: Option<dto::ipv6::Ipv6> = None;

        if let Some(address) = &value.ipv4_address
            && let Some(prefix_length) = &value.ipv4_prefix_length
        {
            let address = cidr::Ipv4Inet::new(address.into(), prefix_length.clone())
                .context(format!("endpoint_id={}", value.endpoint_id))?;
            ipv4 = Some(dto::ipv4::Ipv4 { address })
        }

        if let Some(address) = &value.ipv6_address
            && let Some(prefix_length) = &value.ipv6_prefix_length
        {
            let address = cidr::Ipv6Inet::new(address.try_into()?, prefix_length.clone())
                .context(format!("endpoint_id={}", value.endpoint_id))?;
            ipv6 = Some(dto::ipv6::Ipv6 { address })
        }

        Ok(Self {
            endpoint_id: value.endpoint_id.clone(),
            inner: dto::core::endpoint::Endpoint {
                node_id: value.node_id.clone(),
                endpoint_type: value.endpoint_type.clone().try_into()?,
                name: value.name.clone(),
                description: value.description.clone(),
                ipv4,
                ipv6,
            },
        })
    }
}

impl TryFrom<po::endpoint::Model> for dto::core::endpoint::EndpointItem {
    type Error = anyhow::Error;

    fn try_from(value: po::endpoint::Model) -> Result<Self, Self::Error> {
        dto::core::endpoint::EndpointItem::try_from(&value)
    }
}

impl From<&dto::core::endpoint::UpdateParams> for dao::endpoint::UpdateParams {
    fn from(value: &dto::core::endpoint::UpdateParams) -> Self {
        if value == &Default::default() {
            return Self::default();
        }

        let mut ipv4_address = po::ActiveValue::NotSet;
        let mut ipv4_prefix_length = po::ActiveValue::NotSet;
        if let Some(ip_update_params_opt) = &value.ipv4 {
            match ip_update_params_opt {
                Some(ip_update_params) => {
                    ipv4_address =
                        po::ActiveValue::Set(Some(ip_update_params.address.address().into()));
                    ipv4_prefix_length =
                        po::ActiveValue::Set(Some(ip_update_params.address.network_length()));
                }
                None => {
                    ipv4_address = po::ActiveValue::Set(None);
                    ipv4_prefix_length = po::ActiveValue::Set(None);
                }
            }
        }

        let mut ipv6_address = po::ActiveValue::NotSet;
        let mut ipv6_prefix_length = po::ActiveValue::NotSet;
        if let Some(ip_update_params_opt) = &value.ipv6 {
            match ip_update_params_opt {
                Some(ip_update_params) => {
                    ipv6_address =
                        po::ActiveValue::Set(Some(ip_update_params.address.address().into()));
                    ipv6_prefix_length =
                        po::ActiveValue::Set(Some(ip_update_params.address.network_length()));
                }
                None => {
                    ipv6_address = po::ActiveValue::Set(None);
                    ipv6_prefix_length = po::ActiveValue::Set(None);
                }
            }
        }

        Self {
            name: po::option_to_active_value(value.name.clone()),
            description: po::option_to_active_value(value.description.clone()),
            ipv4_address,
            ipv4_prefix_length,
            ipv6_address,
            ipv6_prefix_length,
        }
    }
}

impl From<&dto::core::endpoint::Filter> for dao::endpoint::Filter {
    fn from(value: &dto::core::endpoint::Filter) -> Self {
        Self {
            node_id: value.node_id.clone(),
            endpoint_id: value.endpoint_id.clone(),
            endpoint_type: value.endpoint_type.as_ref().map(|v| v.as_u32()),
            name: value.name.clone(),
        }
    }
}
